package rabbitMq

import (
	"github.com/Sir-yuChen/funAnalyse/global"
	"github.com/streadway/amqp"
	"go.uber.org/zap"
)

//注意：项目中实际未使用该方式
type RabbitMQ struct {
	Conn    *amqp.Connection
	Channel *amqp.Channel
	// 队列名称
	QueueName string
	// 交换机
	Exchange string
	// routing Key
	RoutingKey string
	//MQ链接字符串
	Mqurl string // MQURL 格式 amqp://账号：密码@rabbitmq服务器地址：端口号/vhost (默认是5672端口)
}

func getMQURL() string {
	return "amqp://" + global.GVA_CONFIG.RabbitMqConfig.Account + ":" + global.GVA_CONFIG.RabbitMqConfig.Password + "@" + global.GVA_CONFIG.RabbitMqConfig.Link + "/"
}

// 优化参考 https://studygolang.com/articles/10617
// LinkRabbitMQ  创建结构体实例
func LinkRabbitMQ(queueName, exchange, routingKey string, b bool) (mq *RabbitMQ, err error) {
	url := "amqp://" + global.GVA_CONFIG.RabbitMqConfig.Account + ":" + global.GVA_CONFIG.RabbitMqConfig.Password + "@" + global.GVA_CONFIG.RabbitMqConfig.Link + "/"
	rabbitMQ := RabbitMQ{
		QueueName:  queueName,
		Exchange:   exchange,
		RoutingKey: routingKey,
		Mqurl:      url,
	}
	//创建rabbitmq连接
	rabbitMQ.Conn, err = amqp.Dial(rabbitMQ.Mqurl)
	if err != nil {
		global.GVA_LOG.Error("RabbitMq创建连接失败", zap.Any("rabbitMQ", rabbitMQ), zap.Error(err))
		return &rabbitMQ, err
	}
	//创建Channel
	rabbitMQ.Channel, err = rabbitMQ.Conn.Channel()
	if err != nil {
		global.GVA_LOG.Error("RabbitMq创建Channel失败", zap.Any("rabbitMQ", rabbitMQ), zap.Error(err))
		return &rabbitMQ, err
	}
	// 1.声明队列
	/*
		如果只有一方声明队列，可能会导致下面的情况：
			a)消费者是无法订阅或者获取不存在的MessageQueue中信息
			b)消息被Exchange接受以后，如果没有匹配的Queue，则会被丢弃
		为了避免上面的问题，所以最好选择两方一起声明
		ps:如果客户端尝试建立一个已经存在的消息队列，Rabbit MQ不会做任何事情，并返回客户端建立成功的
	*/
	_, err = rabbitMQ.Channel.QueueDeclare( // 返回的队列对象内部记录了队列的一些信息，这里没什么用
		queueName, // 队列名
		true,      // 是否持久化
		false,     // 是否自动删除(前提是至少有一个消费者连接到这个队列，之后所有与这个队列连接的消费者都断开时，才会自动删除。注意：生产者客户端创建这个队列，或者没有消费者客户端与这个队列连接时，都不会自动删除这个队列)
		false,     // 是否为排他队列（排他的队列仅对“首次”声明的conn可见[一个conn中的其他channel也能访问该队列]，conn结束后队列删除）
		false,     // 是否阻塞
		nil,       //额外属性（我还不会用）
	)
	if err != nil {
		global.GVA_LOG.Error("RabbitMq队列声明失败", zap.Any("rabbitMQ", rabbitMQ), zap.Error(err))
		return &rabbitMQ, err
	}
	// 2.声明交换器
	err = rabbitMQ.Channel.ExchangeDeclare(
		exchange, //交换器名
		"topic",  //exchange type：一般用fanout、direct、topic
		true,     // 是否持久化
		false,    //是否自动删除（自动删除的前提是至少有一个队列或者交换器与这和交换器绑定，之后所有与这个交换器绑定的队列或者交换器都与此解绑）
		false,    //设置是否内置的。true表示是内置的交换器，客户端程序无法直接发送消息到这个交换器中，只能通过交换器路由到交换器这种方式
		false,    // 是否阻塞
		nil,      // 额外属性
	)
	if err != nil {
		global.GVA_LOG.Error("RabbitMq交换器声明失败", zap.Any("rabbitMQ", rabbitMQ), zap.Error(err))
		return &rabbitMQ, err
	}
	//b true生产者
	if b {
		// 3.建立Binding(可随心所欲建立多个绑定关系)
		err = rabbitMQ.Channel.QueueBind(
			rabbitMQ.QueueName,  // 绑定的队列名称
			rabbitMQ.RoutingKey, // bindkey 用于消息路由分发的key
			rabbitMQ.Exchange,   // 绑定的exchange名
			false,               // 是否阻塞
			nil,                 // 额外属性
		)
		if err != nil {
			global.GVA_LOG.Error("RabbitMq绑定队列和交换器失败", zap.Any("rabbitMQ", rabbitMQ), zap.Error(err))
			return &rabbitMQ, err
		}
	}
	return &rabbitMQ, err
}

// PublishToQueue 发送消息
func (mq *RabbitMQ) PublishToQueue(msg []byte) error {
	mq.Channel.Publish(
		mq.Exchange,   // 交换器名
		mq.RoutingKey, // routing key
		false,         // 是否返回消息(匹配队列)，如果为true, 会根据binding规则匹配queue，如未匹配queue，则把发送的消息返回给发送者
		false,         // 是否返回消息(匹配消费者)，如果为true, 消息发送到queue后发现没有绑定消费者，则把发送的消息返回给发送者
		amqp.Publishing{ // 发送的消息，固定有消息体和一些额外的消息头，包中提供了封装对象
			ContentType: "text/plain", // 消息内容的类型
			Body:        msg,          // 消息内容
		},
	)
	return nil
}

// PublishToQueue 发送消息
func (q *RabbitMQ) Consume() <-chan amqp.Delivery {
	c, e := q.Channel.Consume(
		q.QueueName, //指定从哪个队列中接收消息
		"",
		true,
		false,
		false,
		false,
		nil,
	)
	if e != nil {
		global.GVA_LOG.Error("RabbitMq接收消息异常", zap.Any("rabbitMQ", q), zap.Error(e))
	}
	return c
}

// ReleaseRes 释放资源,建议NewRabbitMQ获取实例后 配合defer使用
func (mq *RabbitMQ) ReleaseRes() {
	mq.Conn.Close()
	mq.Channel.Close()
}
